<?php
/*-------------------------------------------------------------------------\
|
|  ========================================================
|  Fusion Registry GPL
|  Copyright (C) 2007 Fusion Scripts
|
|  This program is free software; you can redistribute it and/or
|  modify it under the terms of the GNU General Public License
|  as published by the Free Software Foundation; either version 2
|  of the License, or (at your option) any later version.
|
|  This program is distributed in the hope that it will be useful,
|  but WITHOUT ANY WARRANTY; without even the implied warranty of
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|  GNU General Public License for more details.
|
|  You should have received a copy of the GNU General Public License
|  along with this program; if not, write to the Free Software
|  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
|  ========================================================
|
|  Email: info@fusionscripts.com
|  Web-Site: http://www.fusionscripts.com/
|
|  $Id: custom_fields.php 4 2008-08-03 01:43:03Z registry.fusion $
|
\-------------------------------------------------------------------------*/

/**
 * Custom fields functions for the Fusion Registry.
 *
 * @package Fusion Registry
 * @subpackage lib
 * @version $Id: custom_fields.php 4 2008-08-03 01:43:03Z registry.fusion $
 */

if ( ! defined( 'IN_IPB' ) )
{
    print "<h1>Incorrect access</h1>You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files.";
    exit();
}

/** Fusion Registry requires */
require_once( ROOT_PATH."sources/handlers/han_parse_bbcode.php" );

/**
 * Class 'registry_custom_fields' contains functions for custom fields.
 *
 * This is the infrastructure for anything related to custom fields
 *
 * @package Fusion Registry
 * @subpackage lib
 */
class registry_custom_fields
{
    /**#@+ @var object */
    var $ipsclass;
    var $class  = "";
    var $parser = "";
    /**#@-*/

    /**#@+
     * Private class arrays, use the appropriate functions instead of accessing these directly
     *
     * @var array
     * @access private
     */
    var $_cf_groups    = array();
    var $_cf_highlight = array();
    var $_makes        = array();
    var $_models       = array();
    /**#@-*/

    /**
     * Constructor, nothing too special here
     *
     * @param object $class Our parent class object
     * @return boolean TRUE
     */
    function auto_run($class)
    {
        $this->class = &$class;

        // Initialize post_parser if it hasn't already
        if ( !is_object( $this->class->lib['parser']) )
        {
            $this->class->lib['parser'] = new parse_bbcode();
            $this->class->lib['parser']->ipsclass =& $this->ipsclass;
        }

        // Setup parser
        $this->class->lib['parser']->parse_smilies = $this->class->registry_config['allow_bbcode_gb'];
        $this->class->lib['parser']->parse_bbcode  = $this->class->registry_config['allow_bbcode_gb'];
        $this->class->lib['parser']->parse_html    = $this->class->registry_config['allow_html_gb'];
        $this->class->lib['parser']->parse_nl2br   = 1;

        return TRUE;
    }


    /**
     * Explode string into associative array
     *
     * Takes a string and makes an array out of it, splitting on the 'glue'
     * parameter. The function {@link process_cf_form} uses this to process
     * custom field options.
     *
     * @param string $glue Delimiter to split string on
     * @param string $str String to split into associative array
     * @return array Associative array
     */
    function explode_assoc($glue,$str)
    {
        // We need to get rid of newlines first, d'oh!
        $str = str_replace("\n", '|', $str);

        // This gives us a flat array
        $arr = explode($glue,$str);

        $size = count($arr);

        // And now we construct our associative array
        for ($i = 0; $i <= ($size - 1); $i += 2)
        {
            $out[ $arr[$i] ] = $arr[$i+1];
        }

        return $out;
    }


    /**
     * Get Custom Field definitions
     *
     * This function's job is only to retrieve the data from the database,
     * it should not process the returned data.
     *
     * @param array $opts Optional custom SQL cmd/value pair attributes
     * @return array An array of an array, outer key is field_id
     */
    function get_cf_defs($opts=array())
    {
        $query = "SELECT field_id, field_name, field_type, field_options,
                    field_default, field_required, field_size, field_rows,
                    field_cols, field_order, field_browse, field_highlight,
                    field_group_id, field_search
                  FROM ibf_registry_field_defs";

        // Did we get any restrictions passed?
        if (count($opts) > 0)
        {
            foreach ($opts as $cmd => $val)
            {
                $query .= " $cmd $val";
            }
        }

        $rs = $this->ipsclass->DB->query($query);

        // Build our data structure to hold the custom fields
        $cf_defs = array();

        while ( $row = $this->ipsclass->DB->fetch_row($rs) )
        {
            $cf_defs[$row['field_id']] = $row;
        }

        return $cf_defs;
    }


    /**
     * Get Custom Field entries
     *
     * This function's job is only to retrieve the data from the database,
     * it should not process the returned data.
     *
     * @param array $opts Optional custom SQL cmd/value pair attributes
     * @return array An array of an array, outer key is field_id
     */
    function get_cf_entries($opts=array())
    {
        $query = "SELECT field_id, field_value FROM ibf_registry_field_entries";

        // Did we get any restrictions passed?
        if (count($opts) > 0)
        {
            foreach ($opts as $cmd => $val)
            {
                $query .= " $cmd $val";
            }
        }

        $rs = $this->ipsclass->DB->query($query);

        // Build our Custom Field entries data structure, we will be using the
        //   same field_id key as the cf_defs data structure as the correlation
        $cf_data = array();

        while ( $row = $this->ipsclass->DB->fetch_row($rs) )
        {
            $cf_data[$row['field_id']] = $row;
        }

        return $cf_data;
    }


    /**
     * Get all the Custom Field Groups, cached if possible
     *
     * This function's job is only to retrieve the data from the database,
     * it should not process the returned data. If this data has already
     * been retrieved and stored into cache it will not query the database.
     *
     * @return array|NULL Success returns an array of an array, outer key is field_group_id; Failure returns NULL.
     */
    function get_cf_groups()
    {
        // Check for cached value
        if ( !empty($this->_cf_groups) )
        {
            return $this->_cf_groups;
        }
        else
        {
            $query = 'SELECT field_id, field_name, field_type, field_options, '.
                        'field_default, field_required, field_size, field_rows, '.
                        'field_cols, field_order, field_browse, field_highlight, '.
                        'field_group_id, field_search '.
                     'FROM ibf_registry_field_defs '.
                     'WHERE field_group_id IS NOT NULL '.
                     'ORDER BY field_group_id ASC, field_order ASC, field_name ASC';

            $rs = $this->ipsclass->DB->query($query);

            // Build our Custom Field Group data structure, we will be returning
            //  a complex structure of arrays of arrays
            $this->_cf_groups = array();

            while ( $row = $this->ipsclass->DB->fetch_row($rs) )
            {
                $this->_cf_groups[$row['field_group_id']][] = $row;
            }

            return $this->_cf_groups;
        }

        // Still here? No soup for you!
        return '';
    }


    /**
     * Get the Custom Field or Group that has been chosen as the highlight item
     *
     * This function's job is only to retrieve the highlight group or item from
     * the database, it should not process the returned data. If this data
     * has already been retrieved and stored into cache it will not query the
     * database.
     *
     * @return array|NULL Success return an array of an array, outer key is field_id; Failure returns NULL.
     */
    function get_cf_highlight()
    {
        // Check for cached value
        if ( !empty($this->_cf_highlight) )
        {
            return $this->_cf_highlight;
        }
        else
        {
            $query = 'SELECT field_id, field_name, field_type, field_options, '.
                            'field_default, field_required, field_size, field_rows, '.
                            'field_cols, field_order, field_browse, field_highlight, '.
                            'field_group_id, field_search '.
                     'FROM ibf_registry_field_defs '.
                     "WHERE field_highlight = '1' ".
                     'ORDER BY field_order ASC, field_name ASC';

            $rs = $this->ipsclass->DB->query($query);

            // If we didn't get any, let's default to the first by order num
            if ( $this->ipsclass->DB->get_num_rows() < 1 )
            {
                $query = 'SELECT field_id, field_name, field_type, field_options, '.
                            'field_default, field_required, field_size, field_rows, '.
                            'field_cols, field_order, field_browse, field_highlight, '.
                            'field_group_id, field_search '.
                         'FROM ibf_registry_field_defs '.
                         "WHERE field_order = '1' LIMIT 1";

                $rs = $this->ipsclass->DB->query($query);
            }

            // Build our Custom Field highlight data structure
            $this->_cf_highlight = array();

            while ( $row = $this->ipsclass->DB->fetch_row($rs) )
            {
                $this->_cf_highlight[$row['field_id']] = $row;
            }

            return $this->_cf_highlight;
        }
    }


    /**
     * Retrieve a make by id, cached if possible
     *
     * Poke the database given a make id to figure out the make name. If
     * this particular make has already been retrieved and saved into cache
     * it will not query the database.
     *
     * @param integer $make_id
     * @return string|NULL Success returns the make name, failure returns NULL.
     */
    function get_make($make_id='')
    {
        // sanitize make_id
        $make_id = intval($make_id);

        // Check for cached value
        if ( isset($this->_makes[$make_id]) )
        {
            return $this->_makes[$make_id];
        }
        else
        {
            // No luck with cache, add it to the collective
            $tmp_row = $this->ipsclass->DB->simple_exec_query( array( 'select' => 'make', 'from' => 'registry_makes', 'where' => "id='$make_id'" ) );

            $this->_makes[$make_id] = $tmp_row['make'];

            return $this->_makes[$make_id];
        }

        // Still here? No soup for you!
        return '';
    }


    /**
     * Retrieve a model by id, cached if possible
     *
     * Poke the database given a model id to figure out the model name. If
     * this particular model has already been retrieved and saved into cache
     * it will not query the database.
     *
     * @param integer $model_id
     * @return string|NULL Success returns the model name, failure returns NULL.
     */
    function get_model($model_id='')
    {
        // Check for cached value
        if ( isset($this->_models[$model_id]) )
        {
            return $this->_models[$model_id];
        }
        else
        {
            // No luck with cache, add it to the collective
            $tmp_row = $this->ipsclass->DB->simple_exec_query( array( 'select' => 'model', 'from' => 'registry_models', 'where' => "id='".intval($model_id)."'" ) );

            $this->_models[$model_id] = $tmp_row['model'];

            return $this->_models[$model_id];
        }

        // Still here? No soup for you!
        return '';
    }


    /**
     * Generate the HTML for custom fields for viewing or form entry
     *
     * Complex routine split up into multiple sub functions to do some of the
     * dirty work and keep the size of this function a little more manageable.
     *
     * @param array $opts Array with keys 'CID' and 'type' with values 'view', 'form', or 'search'
     * @uses registry_custom_fields::get_cf_defs() Gets custom field definitions
     * @uses registry_custom_fields::get_cf_groups() Gets custom field groups
     * @uses registry_custom_fields::process_cf_defs() Combine cf_defs and cf_groups
     * @uses registry_custom_fields::get_cf_entries() Gets custom field entries
     * @uses registry_custom_fields::process_cf_value() Processes the value of a custom field
     * @uses registry_custom_fields::process_cf_form() Processes the input value of a custom field
     * @return array Array of all collected data plus processed html with keys 'cf_defs', 'cf_groups', 'cf_processed', 'cf_entries', 'html'
     */
    function process_cf($opts=array())
    {
        $cf_data = array();

        // First off the custom field definitions
        $cf_data['cf_defs'] = $this->get_cf_defs( array('ORDER BY' => 'field_order ASC') );

        // Grab the custom field groups
        $cf_data['cf_groups'] = $this->get_cf_groups();

        $cf_data['cf_processed'] = $this->process_cf_defs( array( 'cf_defs' => $cf_data['cf_defs'],
                                                                  'cf_groups' => $cf_data['cf_groups'],
                                                                  'type' => 'all',
                                                         )      );

        // Sort it all out
        ksort($cf_data['cf_processed']);

        // Get our custom fields data
        if ( $opts['CID'] )
        {
            $cf_data['cf_entries'] = $this->get_cf_entries( array( 'WHERE' => "item_id='".$opts['CID']."'" ) );
        }
        else
        {
            $cf_data['cf_entries'] = array();
        }


        // Now that all our prep work is done let's go to town on the HTML
        if ( $opts['type'] == 'view' )
        {
            // Loop through all the processed items
            foreach ( $cf_data['cf_processed'] as $item_order => $cf_item )
            {
                // If this is a group it will be an array
                if ( is_array($cf_item[0]) )
                {
                    // Zero out the group html goodies
                    $g_name = '';
                    $g_value = '';
                    $g_highlight = 0;

                    foreach ( $cf_item as $g_order => $cf_group_item )
                    {
                        $g_name .= $cf_group_item['field_name'] .' / ';

                        $g_value .= $this->process_cf_value( array( 'def' => $cf_group_item,
                                                                    'value' => $cf_data['cf_entries'][$cf_group_item['field_id']]['field_value'],
                                                                    'type' => 'view'
                                                           )      );

                        // Add space between values for readability
                        $g_value .= ' ';

                        // This will be used to flag this entire group if any members of the group are set as a highlighted field
                        if ( $cf_group_item['field_highlight'] )
                        {
                            $g_highlight = 1;
                        }
                    }

                    // Get rid of trailing chars
                    $g_name = substr($g_name, 0, -3);
                    $g_value = substr($g_value, 0, -1);

                    // Add this group to the HTML output
                    $cf_data['html'] .= $this->ipsclass->compiled_templates['skin_registry_core']->cf_view( 
                                            array( 'field_name' => $g_name,
                                                   'field_value' => $g_value,
                                                   'field_highlight' => $g_highlight,
                                          )      );
                }
                else
                {
                    // This is a single field, no group
                    $field_value = $this->process_cf_value( array( 'def' => $cf_item,
                                                                   'value' => $cf_data['cf_entries'][$cf_item['field_id']]['field_value'],
                                                                   'type' => 'view',
                                                          )      );

                    $cf_data['html'] .= $this->ipsclass->compiled_templates['skin_registry_core']->cf_view(
                                            array( 'field_name' => $cf_item['field_name'],
                                                   'field_value' => $field_value,
                                                   'field_highlight' => $cf_data['cf_entries'][$cf_item['field_id']]['field_highlight'],
                                          )      );
                }
            }
        }
        elseif ( $opts['type'] == 'form' )
        {
            // Loop through all the processed items
            foreach ( $cf_data['cf_processed'] as $item_order => $cf_item )
            {
                // If this is a group it will be an array
                if ( is_array($cf_item[0]) )
                {
                    // Zero out the group html goodies
                    $g_name = '';
                    $g_value = '';

                    foreach ( $cf_item as $g_order => $cf_group_item )
                    {
                        // Start display of field name
                        $g_name .= $cf_group_item['field_name'];

                        // If this field is required add a red star (only for type=form)
                        if ( $cf_group_item['field_required'] == 1 )
                        {
                            $g_name .= "<span style='color: red;'>*</span>";
                        }

                        // Finish off this field name
                        $g_name .= ' / ';

                        $g_value .= $this->process_cf_form( array( 'def' => $cf_group_item,
                                                                   'value' => $cf_data['cf_entries'][$cf_group_item['field_id']]['field_value'],
                                                          )      );

                        // Add space between values for readability
                        $g_value .= ' ';
                    }

                    // Get rid of trailing chars
                    $g_name = substr($g_name, 0, -3);
                    $g_value = substr($g_value, 0, -1);

                    // Add this group to the HTML output (if we have group data)
                    if ( !empty($g_value) )
                    {
                        $cf_data['html'] .= $this->ipsclass->compiled_templates['skin_registry_core']->cf_form( array( 'field_name' => $g_name,
                                                                                                                       'field_input' => $g_value,
                                                                                                              )      );
                    }
                }
                else
                {
                    // This is a single field, no group
                    $field_name = $cf_item['field_name'];

                    // If this field is required add a red star
                    if ( $cf_item['field_required'] == 1 )
                    {
                        $field_name .= "<span style='color: red;'>*</span>";
                    }

                    $field_input = $this->process_cf_form( array( 'def' => $cf_item,
                                                                  'value' => $cf_data['cf_entries'][$cf_item['field_id']]['field_value'],
                                                         )      );

                    // Add this to the HTML output
                    $cf_data['html'] .= $this->ipsclass->compiled_templates['skin_registry_core']->cf_form( array( 'field_name' => $field_name,
                                                                                                                   'field_input' => $field_input,
                                                                                                          )      );
                }
            }
        }
        elseif ($opts['type'] == 'search')
        {
            // Loop through all the processed items
            foreach ( $cf_data['cf_defs'] as $item_order => $cf_item )
            {
                // Skip any non-searchable fields
                if ( $cf_item['field_search'] != 1 )
                {
                    continue;
                }

                $field_input = $this->process_cf_form( array( 'def' => $cf_item,
                                                              'value' => $cf_data['cf_entries'][$cf_item['field_id']]['field_value'],
                                                     )      );

                // Add this to the HTML output
                $cf_data['html'] .= $this->ipsclass->compiled_templates['skin_registry_main']->search_item( array( 'field_id' => $cf_item['field_id'],
                                                                                                                   'field_name' => $cf_item['field_name'],
                                                                                                                   'field_input' => $field_input,
                                                                                                          )      );
            }
        }

        return $cf_data;
    }


    /**
     * Weld together the custom field groups and custom fields
     *
     * The purpose of this function is to provide a processed list of unique
     * fields. Basically if a field is assigned to a group, it will not be
     * returned as a single field, but rather only in the group it is associated
     * with.
     *
     * @param array $opts Array with keys 'type', 'cf_defs', 'cf_groups'
     * @return array Either one or two layer array (with outer key 'field_order'), based on 'type'
     */
    function process_cf_defs($opts=array())
    {
        $cf = array();

        // General welding
        if ($opts['type'] == 'all')
        {
            // First the cf_groups
            if ( isset($opts['cf_groups']) )
            {
                foreach ( $opts['cf_groups'] as $cf_g_id => $cf_group_rows )
                {
                    // The cf key here is the first field order in the cf group
                    $cf[ $cf_group_rows[0]['field_order'] ] = $cf_group_rows;

                    // Now remove these individual fields
                    foreach ( $cf_group_rows as $cf_group_field )
                    {
                        unset($opts['cf_defs'][$cf_group_field['field_id']]);
                    }
                }
            }

            // Then the cf_defs
            if ( isset($opts['cf_defs']) )
            {
                foreach ( $opts['cf_defs'] as $cf_row )
                {
                    $cf[$cf_row['field_order']] = $cf_row;
                }
            }
        }

        // Very confusing arc welding, we have to prepare an array of arrays
        //  that conforms to the form_dropdown() admin skin function input.
        // We'll also be removing single fields that are in groups.
        elseif ($opts['type'] == 'acp_highlight')
        {
            // First glue in the cf_groups
            if ( isset($opts['cf_groups']) )
            {
                foreach ( $opts['cf_groups'] as $cf_g_id => $cf_group_row )
                {
                    $g_ids = '';
                    $g_fields = '';

                    foreach ( $cf_group_row as $cf_group_field )
                    {
                        $g_ids .= $cf_group_field['field_id'].',';
                        $g_fields .= $cf_group_field['field_name'].' / ';

                        // Now remove this individual field
                        unset($opts['cf_defs'][$cf_group_field['field_id']]);
                    }

                    $g_ids = substr($g_ids, 0, -1);
                    $g_fields = substr($g_fields, 0, -3);

                    $cf[] = array( $g_ids, $g_fields );
                }
            }

            // Then the cf_defs
            if ( isset($opts['cf_defs']) )
            {
                foreach ( $opts['cf_defs'] as $cf_row )
                {
                    $cf[] = array( $cf_row['field_id'], $cf_row['field_name'] );
                }
            }
        }

        return $cf;
    }


    /**
     * Determine any and all special fields that are being used
     *
     * Basically this function is re-arranging the cf_defs array to return
     * an array with keys being the special field names. This will be used
     * to help pre-process special handling needed for various special fields,
     * such as the javascript for the make/model drop downs.
     *
     * @param array $opts Array with key 'cf_defs' as returned from function {@link get_cf_defs}
     * @return array Array with keys being the name of the special field names, and only the special fields
     */
    function process_cf_special_defs($opts=array())
    {
        $cf_special_defs = array();

        if ( is_array($opts['cf_defs']) )
        {
            foreach ( $opts['cf_defs'] as $cf_id => $cf_row )
            {
                if ( $cf_row['field_type'] == 'S' )
                {
                    $cf_special_defs[ strtolower($cf_row['field_options']) ] = $cf_row;
                }
            }
        }

        return $cf_special_defs;
    }


    /**
     * Generate the HTML form input for a particular custom field
     *
     * @param array $opts Array with keys 'def' and 'value'
     * @return string HTML source for input field, empty upon failure
     */
    function process_cf_form($opts=array())
    {
        // We need a def!
        if ( empty($opts['def']) )
        {
            return '';
        }

        $field_input = '';

        switch ( $opts['def']['field_type'] )
        {
            // Single line text
            case 'T':
                $maxlength = $opts['def']['field_size']
                           ? $opts['def']['field_size']
                           : '255';

                $value = $opts['value']
                       ? $this->process_cf_value( array( 'def' => $opts['def'], 'value' => $opts['value'], 'type' => 'form' ) )
                       : $this->process_cf_value( array( 'def' => $opts['def'], 'value' => $opts['def']['field_default'], 'type' => 'form' ) );

                $field_input = "<input type='text' name='". $opts['def']['field_id'] ."' value='$value' maxlength='$maxlength' class='forminput' />";

                break;


            // Textarea
            case 'M':
                $cols = $opts['def']['field_cols']
                      ? $opts['def']['field_cols']
                      : '60';

                $rows = $opts['def']['field_rows']
                      ? $opts['def']['field_rows']
                      : '10';

                $value = $opts['value']
                       ? $this->process_cf_value( array( 'def' => $opts['def'], 'value' => $opts['value'], 'type' => 'form' ) )
                       : $this->process_cf_value( array( 'def' => $opts['def'], 'value' => $opts['def']['field_default'], 'type' => 'form' ) );

                $field_input = "<textarea name='". $opts['def']['field_id'] ."' cols='$cols' rows='$rows' style='width:95%' class='forminput'>$value</textarea>";

                break;


            // Money (floating point)
            case 'F':
                $maxlength = $opts['def']['field_size']
                           ? $opts['def']['field_size']
                           : '255';

                $value = $opts['value']
                       ? $this->process_cf_value( array( 'def' => $opts['def'], 'value' => $opts['value'], 'type' => 'form' ) )
                       : $this->process_cf_value( array( 'def' => $opts['def'], 'value' => $opts['def']['field_default'], 'type' => 'form' ) );

                $field_input = "<input type='text' name='". $opts['def']['field_id'] ."' value='$value' maxlength='$maxlength' class='forminput' />";

                break;


            // Radio buttons
            case 'R':
                $lookup = $this->explode_assoc('|', $opts['def']['field_options']);

                $field_input = '';

                foreach ( $lookup as $k => $v )
                {
                    $checked = ( $opts['value'] == $k )
                             ? "checked='checked'"
                             : '';

                    $field_input .= "<input type='radio' name='". $opts['def']['field_id'] ."' value='$k' $checked class='forminput' /> $v<br />\n";
                }

                // Strip off last <br />
                $field_input = substr($field_input, 0, -7);

                break;


            // Check boxes
            case 'C':
                $lookup = $this->explode_assoc('|', $opts['def']['field_options']);

                $values = unserialize($opts['value']);

                $field_input = '';

                foreach ( $lookup as $k => $v )
                {
                    // Should we check this box?
                    if ( is_array($values) )
                    {
                        if ( in_array($k, $values) )
                        {
                            $checked = "checked='checked'";
                        }
                        else
                        {
                            $checked = '';
                        }
                    }

                    $field_input .= "<input type='checkbox' name='". $opts['def']['field_id'] ."[]' value='$k' $checked class='forminput' /> $v<br />\n";
                }

                // Strip off last <br />
                $field_input = substr($field_input, 0, -7);

                break;


            // Select list
            case 'L':
                $lookup = $this->explode_assoc('|', $opts['def']['field_options']);

                // Start the select
                $field_input = "&nbsp;<select name='". $opts['def']['field_id'] ."' class='forminput'>\n";

                foreach ( $lookup as $k => $v )
                {
                    $selected = ( $opts['value'] == $k )
                              ? "selected='selected'"
                              : '';

                    $field_input .= "<option value='$k' $selected>$v</option>\n";
                }

                // Finish the select
                $field_input .= "</select>";

                break;


            // Special fields
            case 'S':
                if ( strtolower($opts['def']['field_options']) == 'make' )
                {
                    $field_input = "&nbsp;<select name='make' onchange=\"updateModelSelect(this.form.model, this.options[this.selectedIndex].text, '');\" class='forminput'>\n".
                                   "<option value=''>{$this->ipsclass->lang['select_model']}</option>\n".
                                   "</select>";
                }
                elseif ( strtolower($opts['def']['field_options']) == 'model' )
                {
                    $field_input = "&nbsp;<select name='model' class='forminput'>\n".
                                   "<option value=''>{$this->ipsclass->lang['any_model']}</option>\n".
                                   "</select>";
                }
                elseif ( strtolower($opts['def']['field_options']) == 'year' )
                {
                    // Grab the current year
                    $current_date = getdate();

                    // Calculate end year based on offset configured
                    $end_year = $current_date['year'] + $this->class->registry_config['year_end'];

                    // A simple check to prevent infinite loop
                    if ( $this->class->registry_config['year_start'] < $end_year )
                    {
                        $field_input = "&nbsp;<select name='". $opts['def']['field_id'] ."' class='forminput'>\n";

                        for ( $year = $end_year; $year >= $this->class->registry_config['year_start']; $year-- )
                        {
                            $selected = ( $year == $opts['value'] )
                                      ? "selected='selected'"
                                      : '';

                            $field_input .= "<option value='$year' $selected>$year</option>\n";
                        }

                        $field_input .= "</select>";
                    }
                }

                break;
        }

        // Return our generated input HTML
        return $field_input;
    }



    /**
     * Process the submitted form input for custom fields
     *
     * This function is in charge of safely parsing end-user entered values for
     * custom fields. We are looping through the defined custom fields, then
     * checking them for value. This is to ensure we only process the fields
     * that are defined.
     *
     * @param array $cf_defs Value from function {@link get_cf_defs}
     * @return array Processed values for all defined custom fields with structure 'field_id' => 'field_value'
     */
    function process_cf_input($cf_defs=array())
    {
    	$cf_input = array();

    	// We need the definitions!
        if ( empty($cf_defs) or !is_array($cf_defs) )
        {
            return $cf_input;
        }

        foreach ( $cf_defs as $cf_id => $cf_row )
        {
            // This 'if' will fail for make & model, we need to special
            //  case them afterwards!
            if ( !empty( $this->ipsclass->input[$cf_id] ) )
            {
                if ( preg_match('/^(T|M)$/', $cf_row['field_type']) )
                {
                    // Single line text, or Textarea
                    $cf_input[$cf_id] = $this->class->lib['parser']->pre_db_parse( $this->ipsclass->input[$cf_id] );
                }
                elseif ( preg_match('/^(R|L)$/', $cf_row['field_type']) )
                {
                    // Radio button, or Select list (both single values)
                    $valid_options = $this->explode_assoc('|', $cf_row['field_options']);

                    // Only configured values are valid!
                    $cf_input[$cf_id] = ( in_array( $this->ipsclass->input[$cf_id], array_keys($valid_options) ) )
                                      ? htmlspecialchars( $this->ipsclass->input[$cf_id] )
                                      : '';
                }
                elseif ( $cf_row['field_type'] == 'F' )
                {
                    // Money (floating point), make sure comma and dollar signs don't cause problems
                    $this->ipsclass->input[$cf_id] = preg_replace('/(\,|&#036;)/', '', $this->ipsclass->input[$cf_id]);

                    $cf_input[$cf_id] = floatval( $this->ipsclass->input[$cf_id] );
                }
                elseif ( $cf_row['field_type'] == 'C' )
                {
                    // Check boxes
                    $valid_options = $this->explode_assoc('|', $cf_row['field_options']);

                    // We need to loop through and only allow valid options through
                    $options = array();

                    foreach ( $this->ipsclass->input[$cf_id] as $cf_value )
                    {
                        $cf_value = htmlspecialchars( $cf_value );

                        // Is this a valid option?
                        if ( in_array( $cf_value, array_keys($valid_options) ) )
                        {
                            $options[] = $cf_value;
                        }
                    }

                    // We are returning a serialized data structure to store to the DB
                    $cf_input[$cf_id] = serialize($options);
                }
                elseif ( $cf_row['field_type'] == 'S' )
                {
                    // The only special field we are dealing with here is the year,
                    //  the make & model will be special cased below.
                    if ( $cf_row['field_options'] == 'year' )
                    {
                        // Validate year format
                        if ( preg_match('/^\d{4}$/', $this->ipsclass->input[$cf_id]) )
                        {
                            $cf_input[$cf_id] = htmlspecialchars( $this->ipsclass->input[$cf_id] );
                        }
                    }
                }
            }
            elseif ( $cf_row['field_type'] == 'S' and preg_match('/^(make|model)$/i', $cf_row['field_options']) )
            {
                // Now we can special case the make & model fields because they
                //  don't use the field_id in the html form.
                if ( !empty( $this->ipsclass->input[$cf_row['field_options']] ) )
                {
                    // Type cast integer
                    $cf_input[$cf_id] = intval( $this->ipsclass->input[$cf_row['field_options']] );
                }
            }
            elseif ( $cf_row['field_type'] == 'C' )
            {
                // This elseif will catch any defined checkbox fields which did not
                //  have any selected values. In this case the field is not returned
                //  in the query string by the browser.
                $cf_input[$cf_id] = '';
            }
        }

        // Return our processed input
        return $cf_input;
    }


    /**
     * Post process the value of a custom field
     *
     * The value of a custom field stored in the database might be the short
     * version, for instance the key of one of the select list options. We also
     * take care of parsing text safely here.
     *
     * @param array $opts Array with keys 'def' and 'value'
     * @return string Processed value of a custom field
     */
    function process_cf_value($opts=array())
    {
        if ( empty($opts['def']) or empty($opts['value']) )
        {
            return '';
        }

        if ( preg_match('/^(L|R)$/', $opts['def']['field_type']) )
        {
            // Select lists, radio buttons
            $lookup = $this->explode_assoc('|', $opts['def']['field_options']);

            return $lookup[$opts['value']];
        }
        elseif ( preg_match('/^(T|M)$/', $opts['def']['field_type']) )
        {
            // Single text lines and text areas. Parse the text 'safely'
            if ( $opts['type'] == 'form' )
            {
                $opts['value'] = $this->class->lib['parser']->pre_edit_parse( $opts['value'] );
            }
            elseif ( $opts['type'] == 'view' )
            {
                $opts['value'] = $this->class->lib['parser']->pre_display_parse( $opts['value'] );
            }

            return $opts['value'];
        }
        elseif ( $opts['def']['field_type'] == 'C' )
        {
            // Check boxes
            $valid_options = $this->explode_assoc('|', $opts['def']['field_options']);

            $selected_options = unserialize($opts['value']);

            $cf_value = '';

            foreach ( $selected_options as $item )
            {
                // Make sure this is a valid option
                if ( in_array( $item, array_keys($valid_options) ) )
                {
                    $cf_value .= $valid_options[$item] .', ';
                }
            }

            $cf_value = substr($cf_value, 0, -2);

            return $cf_value;
        }
        elseif ( $opts['def']['field_type'] == 'F' )
        {
            // Floating point (money)
            return floatval( $opts['value'] );
        }
        elseif ( $opts['def']['field_type'] == 'S' )
        {
            // 'Special' fields
            if ( strtolower($opts['def']['field_options']) == 'make' )
            {
                return $this->get_make( $opts['value'] );
            }
            elseif ( strtolower($opts['def']['field_options']) == 'model' )
            {
                return $this->get_model( $opts['value'] );
            }
            elseif ( strtolower($opts['def']['field_options']) == 'year' )
            {
                return $opts['value'];
            }
            else
            {
                // Catch all for invalid/unspecified special fields
                return $opts['value'];
            }
        }
        else
        {
            // No soup for you! Must be a plain field, no lookup required ;)
            return $opts['value'];
        }
    }
}

?>